/**
 * FssCommandLineClient.java
 */
package fss.client;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.List;
import java.util.logging.Logger;

import fss.common.communication.DownloadRequest;
import fss.common.communication.PingRequest;
import fss.common.communication.QueryResult;
import fss.common.communication.UploadRequest;
import fss.common.communication.UploadResponse;
import fss.core.config.FssProperties;
import fss.core.logger.FssLogger;
import fss.remote.RemoteClient;
import fss.remote.RemoteService;
import fss.remote.util.RemoteFileService;
import fss.remote.util.RemoteFileServiceClient;
import fss.remote.util.RemoteFileServiceRegistry;
import fss.transfer.Transfer;
import fss.transfer.TransferId;
import fss.transfer.TransferManager;
import fss.transfer.TransferUtils;
import fss.util.FssUtils;
import fss.util.rmi.RmiId;
import fss.util.rmi.RmiUtils;

/**
 * The UI on the command line.
 * 
 * @author thinh
 *
 */
public class FssCommandLineClient implements RemoteClient, RemoteFileServiceClient, Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * The data directory (includes the '/').
     */
    private static final String DATA_DIR = "/data";
    /**
     * The id.
     */
    private RmiId id = null;
    /**
     * Home directory for this client.
     */
    private String homedir = null;
    /**
     * Data directory for this client (i.e. the directory where files are stored).
     */
    private String datadir = null;
    /**
     * The transfer manager.
     */
    private TransferManager transferManager = null;
    /**
     * Constructs a client with the specified identifier.
     * @param id the id.
     */
    public FssCommandLineClient(RmiId id) {
        this.id = id;
        homedir = FssProperties.getInstance().getClientHomeDir(id.getName());
        datadir = homedir + DATA_DIR;
        FssUtils.createDirectory(homedir);
        FssUtils.createDirectory(datadir);
        transferManager = new TransferManager(datadir);
    }
    @Override
    public RmiId getId() throws RemoteException {
        return id;
    }
    @Override
    public void update(Object observable, Object message) throws RemoteException {
        Logger logger = FssLogger.getInstance().getClientLogger(id.getName());
        /*
         * Ping Request
         */
        if(message instanceof PingRequest) {
            // do nothing, no need to clutter up log messages
        } 
        /*
         * In this framework, the Transfer manager from a Remote File Service pushes Transfer
         * objects through the java Observer/Obserable framework to the client. 
         */
        else if(message instanceof Transfer) {
            Transfer transfer = (Transfer) message;
            handleTransfer(transfer);
        } 
        /*
         * For an upload, the client is the SOURCE (from TransferId), so the 
         * client must check that it is the one who is expected to process 
         * this upload. 
         */
        else if(message instanceof UploadResponse) {
            UploadResponse response = (UploadResponse) message;
            if(response.getTransferId().getSourceId().equals(id)) {
                logger.fine("Received Upload Response from " + response.getSource() + " and will process.");
                transferManager.process(response);
            } else {
                logger.fine("Ignoring Upload Response from " + response.getSource());
            }
        }
    }
    /**
     * {@inheritDoc}
     * <p>
     * The upload routine for this client will:
     * <ol>
     *  <li>Attempt to connect to the Remote Service identified by <b>destination</b>
     *  identifier in the Upload Request</li>
     *  <li>Register the client to the Remote Service</li>
     *  <li>Invoked <code>processRequest</code> using the Remote Service stub</li>
     * </ol>
     */
    public void upload(UploadRequest request) throws RemoteException {
        // the *service* is the destination since this is an upload
        RmiId serviceId = request.getTransferId().getDestinationId(); 
        RemoteFileService service = (RemoteFileService) RmiUtils.getRemoteObject(serviceId);
        service.register(this);
        service.processRequest(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * To resume an incomplete transfer:
     * <ol>
     *  <li>Iterate through the incomplete transfer list</li>
     *  <li>If the transfer is found, resume the transfer by creating a new Download Request
     *  using the Transfer ID <b>AND</b> the saved Transfer offset position of the data</li>
     *  <li>If the transfer is not found, throw a Remote Exception</li>
     * </ol>
     */
    public void resume(TransferId transferId) throws RemoteException {
        Logger logger = FssLogger.getInstance().getClientLogger(id.getName());

        boolean found = false;
        List<Transfer> incomplete = TransferUtils.getIncompleteTransferList(datadir);
        for(Transfer transfer : incomplete) {
            if(transfer.getId().equals(transferId)) {
                logger.info("Incomplete Transfer found: " + transfer);
                DownloadRequest request = new DownloadRequest(id, transferId);
                request.setOffset(transfer.getOffset()); // set the offset
                download(request);
                found = true;
                break; // no need to continue looking through incomplete transfer list
            }
        }
        if(!found) {
            throw new RemoteException("Cannot find incomplete transfer for Transfer ID: " + transferId);
        }
    }
    /**
     * {@inheritDoc}
     * <p>
     * The downlaod routine for this client will:
     * <ol>
     *  <li>Attempt to connect to the Remote Service identified by <b>source</b>
     *  identifier in the Download Request</li>
     *  <li>Invoked <code>processRequest</code> using the Remote Service stub</li>
     * </ol>
     */
    public void download(DownloadRequest request) throws RemoteException {
        // the *service* is the source since this is a download
        RmiId serviceId = request.getTransferId().getSourceId();
        RemoteService service = (RemoteService) RmiUtils.getRemoteObject(serviceId);
        if(service == null) throw new RemoteException("Cannot connect to the RemoteService " + serviceId);
        service.processRequest(request);
    }
    /**
     * {@inheritDoc}
     * <p>
     * The query routine will:
     * <ol>
     *  <li>Attempt to connect to the Remote File Service Registry identified by its RMI location</li>
     *  <li>Invoke the <code>query</coded> (remote method)</li>
     * </ol>
     */
    public List<QueryResult> query(RmiId registryId, String param) throws RemoteException {
        RemoteFileServiceRegistry registry = (RemoteFileServiceRegistry)RmiUtils.getRemoteObject(registryId);
        if(registry == null) throw new RemoteException("Cannot connect to the Remote File Service Registry " + registryId);
        
        List<QueryResult> results = null;
        results = registry.query(param);
        return results;
    }
//    @Override
//    public void update(Observable o, Object message) {
//        Logger logger = FssLogger.getInstance().getClientLogger(id.getName());
//        try {
//            logger.info("Within the update(Observable,Object) method of Java with message: " + message.getClass().getName());
//            update((Object)o, message);
//        } catch (RemoteException e) {
//            logger.severe("This update method is not supposed to be used: " + e);
//            o.deleteObserver(this);
//        }
//    }
    @Override
    public void stop() throws RemoteException {
        FssLogger.getInstance().getClientLogger(id.getName()).fine("The method stop() in " + FssCommandLineClient.class.getName() + " is not implemented.");
        // Not implemented (for now)
    }
    @Override
    public void start() throws RemoteException {
        FssLogger.getInstance().getClientLogger(id.getName()).fine("The method stop() in " + FssCommandLineClient.class.getName() + " is not implemented.");
        // Not implemented (for now)
    }
    /**
     * Get the home directory for this client.
     * @return the homedir.
     */
    public String getHomedir() {
        return homedir;
    }
    /**
     * Get the data directory for this client.
     * @return the datadir.
     */
    public String getDatadir() {
        return datadir;
    }
    /**
     * Handle a download request transfer.
     * @param transfer the transfer.
     */
    private void handleTransfer(Transfer transfer) {
        Logger logger = FssLogger.getInstance().getClientLogger(id.getName());
        // only process if this client is the intended destination
        if(transfer.getId().getDestinationId().equals(id)) {
            TransferUtils.handleTransfer(logger, datadir, transfer);
//            String persistedFilename = datadir + FssUtils.getSystemFileSeparator() + TransferUtils.getTransferFilename(transfer.getId());
//            
//            File transferFile = new File(persistedFilename);
//            // this is the first received chunk, just serialize it
//            if(!transferFile.exists()) {
//                SerializableUtils.serializeObject(transfer, persistedFilename);
//                logger.info("Initial Transfer serialized to " + persistedFilename);
//            } 
//            // processing the transfer chunk
//            else {
//                Transfer previousTransfer = (Transfer) SerializableUtils.deserializeObject(persistedFilename);
//                logger.info("\nPrevious Transfer: " + previousTransfer + "\nReceived Transfer: " + transfer);
//                
//                if(transfer.getStatus() == StatusType.COMPLETED) {
//                    logger.info("Download transfer is complete. Removing serialized chunk " + 
//                            persistedFilename + " (" + FssUtils.removeFile(persistedFilename) + ")");
//                    try {
//                        TransferUtils.convertTransferToFile(previousTransfer, datadir);
//                    } catch (IOException e) {
//                        e.printStackTrace();
//                    }
//                } else {
//                    // update the previous transfer chunk with the updated 
//                    TransferUtils.combineTransfer(previousTransfer, transfer);
//
//                    // serialize the file over the old file
//                    SerializableUtils.serializeObject(previousTransfer, persistedFilename);
//                }
//            }
        }
    }
}
